package com.bj58.mis.apm.server.kylin.model;

import com.bj58.mis.apm.server.kylin.common.util.KylinConfig;
import com.bj58.mis.apm.server.kylin.util.ComparUtil;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import javax.annotation.Nullable;
import java.util.*;

public class CuboidScheduler {
    private KylinConfig cfg;
    protected CubeDesc cubeDesc;
    private transient List<List<Long>> cuboidsByLayer;
    private long max;
    private Set<Long> allCuboidIds;
    private Map<Long, List<Long>> parent2child;
    private Map<Long, Long> child2parent;

    private List<AggregationGroup> aggregationGroups;

    public CuboidScheduler(CubeDesc cubeDesc) {
        cfg = new KylinConfig();
        this.cubeDesc = cubeDesc;
        int size = cubeDesc.getRowKeyDescs().size();
        this.max = (long)Math.pow(2.0D, (double)size) - 1L;
        this.aggregationGroups = AggregationGroup.buildGroups(cubeDesc, this.cfg);
        this.buildTreeBottomUp();
    }

    private void buildTreeBottomUp() {
        int forward = this.cubeDesc.getParent_forward();
        Set<Long> cuboidHolder = new HashSet();
        Set<Long> children = this.getLowestCuboids();
        long maxCombination = this.cfg.getCubeAggrGroupMaxCombination() * 10L;

        for(maxCombination = maxCombination < 0L ? 9223372036854775807L : maxCombination; !children.isEmpty(); children = this.getOnTreeParentsByLayer(children)) {
            if ((long)cuboidHolder.size() > maxCombination) {
                throw new IllegalStateException("Too many cuboids for the cube. Cuboid combination reached " + cuboidHolder.size() + " and limit is " + maxCombination + ". Abort calculation.");
            }

            cuboidHolder.addAll(children);
        }

        cuboidHolder.add(this.max);
        cuboidHolder = Sets.newHashSet(Iterators.filter(cuboidHolder.iterator(), new Predicate<Long>() {
            public boolean apply(@Nullable Long cuboidId) {
                return !CuboidScheduler.this.cubeDesc.getCuboid_black_list().contains(cuboidId);
            }
        }));
        Map<Long, List<Long>> parent2Child = Maps.newHashMap();
        Queue<Long> cuboidScan = new ArrayDeque();
        cuboidScan.addAll(cuboidHolder);

        while(!cuboidScan.isEmpty()) {
            long current = (Long)cuboidScan.poll();
            long parent = this.getParentOnPromise(current, cuboidHolder, forward);
            if (parent > 0L) {
                if (!cuboidHolder.contains(parent)) {
                    cuboidScan.add(parent);
                    cuboidHolder.add(parent);
                }

                if (parent2Child.containsKey(parent)) {
                    ((List)parent2Child.get(parent)).add(current);
                } else {
                    parent2Child.put(parent, Lists.newArrayList(new Long[]{current}));
                }
            }
        }

        this.allCuboidIds = cuboidHolder;
        this.parent2child = parent2Child;

        this.child2parent = new HashMap<>();
        this.child2parent.put(this.max, 0L);
        Set<Long> ps = this.parent2child.keySet();
        for(Long p : ps){
            this.parent2child.get(p).forEach( c -> {
                this.child2parent.put(c, p);
            });
        }
    }

    private Set<Long> getLowestCuboids() {
        return this.getOnTreeParents(0L, this.aggregationGroups);
    }

    private Set<Long> getOnTreeParents(long child, Collection<AggregationGroup> groups) {
        Set<Long> parentCandidate = new HashSet();
        if (child == this.max) {
            return parentCandidate;
        } else {
            Iterator i$ = groups.iterator();

            while(i$.hasNext()) {
                AggregationGroup agg = (AggregationGroup)i$.next();
                if (child == agg.getPartialCubeFullMask()) {
                    parentCandidate.add(this.max);
                    return parentCandidate;
                }

                parentCandidate.addAll(this.getOnTreeParents(child, agg));
            }

            return parentCandidate;
        }
    }

    private Set<Long> getOnTreeParents(long child, AggregationGroup agg) {
        Set<Long> parentCandidate = new HashSet();
        long tmpChild = child;
        if (child == agg.getPartialCubeFullMask()) {
            return parentCandidate;
        } else {
            if (agg.getMandatoryColumnMask() != 0L) {
                if (agg.isMandatoryOnlyValid()) {
                    if (this.fillBit(child, agg.getMandatoryColumnMask(), parentCandidate)) {
                        return parentCandidate;
                    }
                } else {
                    tmpChild = child | agg.getMandatoryColumnMask();
                }
            }

            Iterator i$ = agg.getNormalDims().iterator();

            Long joint;
            while(i$.hasNext()) {
                joint = (Long)i$.next();
                this.fillBit(tmpChild, joint, parentCandidate);
            }

            i$ = agg.getJoints().iterator();

            while(i$.hasNext()) {
                joint = (Long)i$.next();
                this.fillBit(tmpChild, joint, parentCandidate);
            }

            i$ = agg.getHierarchyMasks().iterator();

            while(i$.hasNext()) {
                AggregationGroup.HierarchyMask hierarchy = (AggregationGroup.HierarchyMask) i$.next();
                long[] arr$ = hierarchy.allMasks;
                int len$ = arr$.length;

                for(int i = 0; i < len$; ++i) {
                    long mask = arr$[i];
                    if (this.fillBit(tmpChild, mask, parentCandidate)) {
                        break;
                    }
                }
            }

            return parentCandidate;
        }
    }

    private Set<Long> getOnTreeParentsByLayer(Collection<Long> children) {
        Set<Long> parents = new HashSet();
        Iterator i$ = children.iterator();

        while(i$.hasNext()) {
            long child = (Long)i$.next();
            parents.addAll(this.getOnTreeParents(child));
        }

        parents = Sets.newHashSet(Iterators.filter(parents.iterator(), new Predicate<Long>() {
            public boolean apply(@Nullable Long cuboidId) {
                if (cuboidId == CuboidScheduler.this.max) {
                    return true;
                } else {
                    Iterator i$ = CuboidScheduler.this.aggregationGroups.iterator();

                    AggregationGroup agg;
                    do {
                        if (!i$.hasNext()) {
                            return false;
                        }

                        agg = (AggregationGroup)i$.next();
                    } while(!agg.isOnTree(cuboidId) || !CuboidScheduler.this.checkDimCap(agg, cuboidId));

                    return true;
                }
            }
        }));
        return parents;
    }
    private boolean checkDimCap(AggregationGroup agg, long cuboidID) {
        int dimCap = agg.getDimCap();
        if (dimCap <= 0) {
            return true;
        } else {
            return Long.bitCount(cuboidID) <= dimCap;
        }
    }
    private long getParentOnPromise(long child, Set<Long> coll, int forward) {
        long parent = this.getOnTreeParent(child);
        if (parent < 0L) {
            return -1L;
        } else {
            return !coll.contains(parent) && forward != 0 ? this.getParentOnPromise(parent, coll, forward - 1) : parent;
        }
    }

    private long getOnTreeParent(long child) {
        Collection<Long> candidates = this.getOnTreeParents(child);
        return candidates != null && !candidates.isEmpty() ? (Long)Collections.min(candidates, ComparUtil.cuboidSelectComparator) : -1L;
    }

    private Set<Long> getOnTreeParents(long child) {
        List<AggregationGroup> aggrs = Lists.newArrayList();
        Iterator i$ = this.aggregationGroups.iterator();

        while(i$.hasNext()) {
            AggregationGroup agg = (AggregationGroup)i$.next();
            if (agg.isOnTree(child)) {
                aggrs.add(agg);
            }
        }

        return this.getOnTreeParents(child, (Collection)aggrs);
    }

    private boolean fillBit(long origin, long other, Set<Long> coll) {
        if ((origin & other) != other) {
            coll.add(origin | other);
            return true;
        } else {
            return false;
        }
    }

    public List<List<Long>> getCuboidsByLayer() {
        if (this.cuboidsByLayer != null) {
            return this.cuboidsByLayer;
        } else {
            int totalNum = 0;
            this.cuboidsByLayer = Lists.newArrayList();
            this.cuboidsByLayer.add(Collections.singletonList(this.max));
            totalNum = totalNum + 1;

            ArrayList newLayer;
            for(Object lastLayer = (List)this.cuboidsByLayer.get(this.cuboidsByLayer.size() - 1); !((List)lastLayer).isEmpty(); lastLayer = newLayer) {
                newLayer = Lists.newArrayList();
                Iterator i$ = ((List)lastLayer).iterator();

                while(i$.hasNext()) {
                    Long parent = (Long)i$.next();
                    newLayer.addAll(this.getSpanningCuboid(parent));
                }

                if (newLayer.isEmpty()) {
                    break;
                }

                this.cuboidsByLayer.add(newLayer);
                totalNum += newLayer.size();
            }

            int size = this.getAllCuboidIds().size();
            Preconditions.checkState(totalNum == size, "total Num: " + totalNum + " actual size: " + size);
            return this.cuboidsByLayer;
        }
    }

    public List<Long> getSpanningCuboid(long cuboid) {
        if (cuboid <= this.max && cuboid >= 0L) {
            List<Long> spanning = (List)this.parent2child.get(cuboid);
            return spanning == null ? Collections.EMPTY_LIST : spanning;
        } else {
            throw new IllegalArgumentException("Cuboid " + cuboid + " is out of scope 0-" + this.max);
        }
    }

    public CubeDesc getCubeDesc() {
        return cubeDesc;
    }

    public long getMax() {
        return max;
    }

    public Set<Long> getAllCuboidIds() {
        return allCuboidIds;
    }

    public Map<Long, List<Long>> getParent2child() {
        return parent2child;
    }

    public List<AggregationGroup> getAggregationGroups() {
        return aggregationGroups;
    }

    public Map<Long, Long> getChild2parent() {
        return child2parent;
    }
}
